package jmetal.metaheuristics.nsgaII;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import jmetal.core.Algorithm;
import jmetal.core.Operator;
import jmetal.core.Problem;
import jmetal.core.Solution;
import jmetal.core.SolutionSet;
import jmetal.util.ExtremumPoint;
import jmetal.util.JMException;
import jmetal.util.MatrixUtil;
import jmetal.util.Ranking;
import jmetal.util.SingletonExtremePoint;
import jmetal.util.SingletonReferenceVector;
import jmetal.util.comparators.FitnessComparator;
import jmetal.util.dir.NearestSolutionFitness;
import jmetal.util.dir.OccupyFitness;


public class RAN extends Algorithm{
	/**
	 * 两处调整
	 * 1、调整计算距离的参数
	 * 2、要不要>THETA的判断
	 */
	private static final long serialVersionUID = 6491179879661463650L;
	Operator mutationOperator;
	Operator crossoverOperator;
	Operator selectionOperator;
	int populationSize;
	int maxEvaluations;
	int evaluations;//当前evaluation 次数
    double THETA;
	double[] zideal;
	double[] intercepts;
	double[][] vectors;
	
	public RAN(Problem problem) {
		super(problem);
	}


	@Override
	public SolutionSet execute() throws JMException, ClassNotFoundException {

		initializeAlg();
		
		
		SolutionSet population = initilizePopulation(); 
//		int itr = 0;
		generate(population);
		SingletonExtremePoint.freePoint();
		Ranking ranking = new Ranking(population);
		return ranking.getSubfront(0);
	}


	private void generate(SolutionSet population) throws JMException {
		int itr = 0;
		while(evaluations < maxEvaluations){
//			DirFitness popufitness = new DirFitness(population, populationSize,problem_);
//			popufitness.assignFitness(0);
			
			SolutionSet offspringPopulation = generateOffspring(population);
			SolutionSet union = population.union(offspringPopulation);
			
			zideal = ExtremumPoint.getIdealPoint(union);
			intercepts = ExtremumPoint.getInterceptPoint(union);
			
			Ranking rank = new Ranking(union);
	        int remain = populationSize;
	        int index = 0;
	        SolutionSet front = null;
	        population.clear();
	        
	        front = rank.getSubfront(index);
	        while(remain>0&&remain>front.size()){
				for (int k = 0; k < front.size(); k++) {
					population.add(front.get(k));
				}
				remain = remain-front.size();
				index++;
				if(remain>0){
					front = rank.getSubfront(index);
				}
	        }
	        int numberOfNondomi = population.size();
	        if(remain>0){
	        	SolutionSet unionSet = population.union(front);
	        	OccupyFitness fitness = new OccupyFitness(unionSet);
	        	fitness.assignFitness(vectors,zideal,intercepts);
	        	
	        	@SuppressWarnings("rawtypes")
				Comparator comparator = new FitnessComparator();
	        	
	        	front.sort(comparator);
	        	
	        	List<SolutionSet> fronts = getFrontsByFitness(front);
	        	int i = 0;
	        	while(remain>fronts.get(i).size()){
		        	for(int j = 0;j<fronts.get(i).size();j++){
		        		population.add(fronts.get(i).get(j));
		        	}
	        		remain-=fronts.get(i).size();
	        		i++;
	        	}
	        	SolutionSet remainFront = fronts.get(i);
        		NearestSolutionFitness nearestFitness = new NearestSolutionFitness(remainFront);
        		nearestFitness.assignFitness(zideal,intercepts,population);
	        	while(remain>0){

	        		int maxIndex = getIndexOfMaxSolution(remainFront);
	        		Solution maxSolution = remainFront.get(maxIndex);
					population.add(maxSolution);
					remainFront.remove(maxIndex);
					remain--;
					updateNearestFitness(maxSolution,remainFront);
	        	}
	        	
	        	SolutionSet uncatchedSolution = getUncatchedSolution(fronts, i, remainFront);
	        	regeneration(population,numberOfNondomi,uncatchedSolution,itr);
	        	
	        	
	        }
	        itr++;
//	        System.out.println(itr++);
		}
	}


	private void updateNearestFitness(Solution maxSolution, SolutionSet remainFront) {
		for(int i = 0;i<remainFront.size();i++){
			Solution solution = remainFront.get(i);
			double tempSin = MatrixUtil.sin(solution.getObjectives(), maxSolution.getObjectives());
			if(tempSin<solution.getNearestSolDis())
				solution.setNearestSolDis(tempSin);
			
		}
		
	}


	private int getIndexOfMaxSolution(SolutionSet remainFront) {
		int maxIndex = 0;
		double maxAngle =0;
		for(int i = 0;i<remainFront.size();i++){
			double tempAngle = remainFront.get(i).getNearestSolDis();
			if(tempAngle>maxAngle){
				maxIndex = i;
				maxAngle = tempAngle;
			}
		}
		return maxIndex;
	}


	private void regeneration(SolutionSet population,int numberOfNondomi, SolutionSet uncatchedSolution,int itr) {
		for(int i = 0;i<uncatchedSolution.size();i++){
			Solution solution = uncatchedSolution.get(i);
			int index = getNearestSolution(population,numberOfNondomi,solution);
			double[] normObjectives1 = normSolution(solution.getObjectives());
			double[] normObjectives2 = normSolution(population.get(index).getObjectives());
			double minSin = MatrixUtil.sin(normObjectives1,normObjectives2);
			
			//=======================================PLUS
			double distance = MatrixUtil.norm(normObjectives1);
			double distance2 = MatrixUtil.norm(normObjectives2);
			if(distance<distance2&&(distance2-distance)>Math.asin(minSin)){
				population.set(index, solution);
			}
			//========================================
//			if(minSin<THETA){
//				//距离范数调整
//				double distance = MatrixUtil.norm(normObjectives1);
//				double distance2 = MatrixUtil.norm(normObjectives2);
//				if(distance<distance2&&(distance2-distance)>THETA/2){
//					population.set(index, solution);
//				}
//			}
		}
		
	}


	private double[] normSolution(double[] objectives) {
		double[] normVector = new double[objectives.length];
		for(int i = 0;i<objectives.length;i++){
			normVector[i] = (objectives[i]-zideal[i])/(intercepts[i]-zideal[i]);
		}
		return normVector;
	}


	/**
	 * 最近的解的下标
	 * @param population
	 * @param numberOfNondomi 
	 * @param solution
	 * @return
	 */
	private int getNearestSolution(SolutionSet population, int numberOfNondomi, Solution solution) {
		int min = 0;
		double minSin = Double.MAX_VALUE;
		for(int i = numberOfNondomi;i<population.size();i++){
			double sin = MatrixUtil.sin(solution.getObjectives(),population.get(i).getObjectives());
			if(sin<minSin){
				minSin = sin;
				min = i;
			}
		}
		return min;
		
	}


	/**
	 * 添加未被加入的非支配解
	 * @param front
	 * @param fronts
	 * @param i
	 * @param remainFront
	 * @return
	 */
	private SolutionSet getUncatchedSolution(List<SolutionSet> fronts, int i, SolutionSet remainFront) {
		SolutionSet regenerationSet = new SolutionSet(2*populationSize);
		for(int reIndex = 0;reIndex<remainFront.size();reIndex++){
			regenerationSet.add(remainFront.get(reIndex));
		}
		i++;
		while(i<fronts.size()){
			SolutionSet solutionSet = fronts.get(i++);
			for(int j = 0;j<solutionSet.size();j++){
				regenerationSet.add(solutionSet.get(j));
			}
		}
		return regenerationSet;
	}




	/**
	 * 根据fitness分层
	 * @param front
	 * @return
	 */
	private List<SolutionSet> getFrontsByFitness(SolutionSet front) {
		List<SolutionSet> fronts = new ArrayList<SolutionSet>();
		int i = 0;
		while(i<front.size()-1){
			SolutionSet solutionSet = new SolutionSet(front.size());
			do{
				solutionSet.add(front.get(i));
				i++;
			}while(i<front.size()&&front.get(i-1).getFitness()==front.get(i).getFitness());
			fronts.add(solutionSet);
		}
		return fronts;
	}


	private SolutionSet generateOffspring(SolutionSet population) throws JMException {
		SolutionSet offspringPopulation;
		offspringPopulation = new SolutionSet(populationSize);
		Solution[] parents = new Solution[2];
		for(int i=0;i<(populationSize/2);i++){
			if(evaluations<maxEvaluations){
				parents[0] = (Solution) selectionOperator.execute(population);
				parents[1] = (Solution) selectionOperator.execute(population);
				Solution[] offSpring = (Solution[]) crossoverOperator.execute(parents);
				mutationOperator.execute(offSpring[0]);
				mutationOperator.execute(offSpring[1]);
				problem_.evaluate(offSpring[0]);
				problem_.evaluateConstraints(offSpring[0]);
				problem_.evaluate(offSpring[1]);
				problem_.evaluateConstraints(offSpring[1]);
				offspringPopulation.add(offSpring[0]);
				offspringPopulation.add(offSpring[1]);
				evaluations += 2;
			}
		}
		return offspringPopulation;
	}

	private void initializeAlg() {
		// Read the parameters
		populationSize = ((Integer) getInputParameter("populationSize")).intValue();
		maxEvaluations = ((Integer) getInputParameter("maxEvaluations")).intValue();
		evaluations = 0;
		// Read the operators
		mutationOperator = operators_.get("mutation");
		crossoverOperator = operators_.get("crossover");
		selectionOperator = operators_.get("selection");
		
		/**
		 * 参数！！！
		 */
		THETA = 0.0003*Math.pow(problem_.getNumberOfObjectives(), 2);
		
		int numberObjectives = problem_.getNumberOfObjectives();
		zideal = new double[numberObjectives];
		intercepts = new double[numberObjectives];
		vectors = SingletonReferenceVector.getReferenceVectors(numberObjectives, populationSize);
	}

	
	private SolutionSet initilizePopulation()
			throws ClassNotFoundException, JMException {
		SolutionSet population = new SolutionSet(populationSize);
		Solution newSolution;
		for (int i = 0; i < populationSize; i++) {
			newSolution = new Solution(problem_);
			problem_.evaluate(newSolution);
			problem_.evaluateConstraints(newSolution);
			evaluations++;
			population.add(newSolution);
		}
		return population;
	}
	
}
